﻿$import( "SmitearWebGL.Core.ResourceItem" );
$import( "SmitearWebGL.Resources.Shader" );

SmitearWebGL.Resources.SurfaceMaterial = SmitearWebGL.Core.ResourceItem.extend(
{
	/******** constructor ********/

	init : function()
	{
		this._super();
		this._registClass( "SmitearWebGL.Resources.SurfaceMaterial" );

		/******** public property ********/

		this.ambient = null;
		this.diffuse = null;
		this.specular = null;
		this.emissive = null;
		this.shininess = 8.0;
		this.reflect = 0.8;
		this.alpha = 1.0;
		this.doubleSide = true;
		this.renderMode = SmitearWebGL.Resources.SurfaceMaterial.renderMode.TRIANGLE;

		/******** private property ********/

		this._uniformStrings = null;
		this._implementStrings = null;
		this._layers = null;
	}
	,

	/******** public method ********/
	
	getUniformStrings : function()
	{
		if ( ! this._uniformStrings )
			this._generateShaderUniform();
		
		return this._uniformStrings;
	}
	,
	getImplementStrings : function()
	{
		if ( ! this._implementStrings )
			this._generateShaderImplement();
		
		return this._implementStrings;
	}
	,
	glUniform : function( field, shader )
	{
		switch( field )
		{
			case SmitearWebGL.ShaderUniform.ambient:
				this.context.gl.uniform3fv( shader.uniforms.uAmbient.location, new Float32Array( this.ambient.flatten3v() ) );
				break;
				
			case SmitearWebGL.ShaderUniform.diffuse:
				this.context.gl.uniform3fv( shader.uniforms.uDiffuse.location, new Float32Array( this.diffuse.flatten3v() ) );
				break;
				
			case SmitearWebGL.ShaderUniform.emissive:
				this.context.gl.uniform3fv( shader.uniforms.uEmissive.location, new Float32Array( this.emissive.flatten3v() ) );
				break;
				
			case SmitearWebGL.ShaderUniform.specular:
				this.context.gl.uniform3fv( shader.uniforms.uSpecular.location, new Float32Array( this.specular.flatten3v() ) );
				break;
				
			case SmitearWebGL.ShaderUniform.shininess:
				this.context.gl.uniform1f( shader.uniforms.uShininess.location, this.shininess / 1024.0 );
				break;
				
			case SmitearWebGL.ShaderUniform.reflect:
				this.context.gl.uniform1f( shader.uniforms.uReflective.location, this.reflect );
				break;
				
			case SmitearWebGL.ShaderUniform.alpha:
				this.context.gl.uniform1f( shader.uniforms.uAlpha.location, this.alpha );
				break;
		}
	}
	,
	getLayers : function()
	{
		return this._layers;
	}
	,
	getLayerByName : function( name )
	{
		for ( var i = 0; i < this._layers.length; ++ i )
		{
			if ( this._layers[i].name == name )
			{
				return this._layers[i];
			}
		}
		
		return null;
	}
	,
	getLayerAt : function( index )
	{
		return this._layers[index];
	}
	,
	addLayer : function( layer )
	{
		this._layers.push( layer );
		return this._layers.length - 1;
	}
	,
	removeLayer : function( layer )
	{
		for ( var i = 0; i < this._layers.length; ++ i )
		{
			if ( layer === this._layers[i] )
				return this.removeLayerAt( i );
		}
	}
	,
	removeLayerAt : function( index )
	{
		var layer = this._layers.splice( index, 1 )[0];
		
		if ( this._layers.length == 0 )
			this._layers = null;
			
		return layer
	}
	,
	removeLayerByName : function( name )
	{
		for ( var i = 0; i < this._layers.length; ++ i )
		{
			if ( this._layers[i].name == name )
				return this.removeLayerAt( i );
		}
		
		return null;
	}
	,
	replaceLayerAt : function( layer, index )
	{
		this._layers[index] = layer;
	}
	,
	swapLayer : function( layer1, layer2 )
	{
		var tmp = this._layers[layer1];
		this._layers[layer1] = this._layers[layer2];
		this._layers[layer2] = tmp;
	}
	,

	/******** inherit method ********/
	
	_generateShaderUniform : function()
	{
		var vertStr = [];
		var fragStr = [];
		var layers = this.getLayers(), texture, layer;
		
		//vs
		for( var i = 0; i < layers.length; i ++ )
		{
			layer = this._layers[i];
			vertStr.push( "uniform mat4 uLayer" + layer.getResourceHandle() + "Matrix;\n" );
			vertStr.push( "varying vec3 vTextureCoords" + i + ";\n" );
		}
		
		//fs
		fragStr.push( "uniform vec3 uAmbientColor;\n" );
		fragStr.push( "uniform vec3 uAmbient;\n" );
		fragStr.push( "uniform vec3 uDiffuse;\n" );
		fragStr.push( "uniform vec3 uSpecular;\n" );
		fragStr.push( "uniform vec3 uEmissive;\n" );
		fragStr.push( "uniform float uWndWidth;\n" );
		fragStr.push( "uniform float uWndHeight;\n" );
		fragStr.push( "uniform sampler2D uDiffMap;\n" );
		fragStr.push( "uniform sampler2D uSpecMap;\n" );
		fragStr.push( "uniform sampler2D uAOMap;\n" );
		
		for ( var i = 0; i < layers.length; i ++ )
		{
			layer = layers[i];
			texture = layer.getTexture();
			
			if ( texture )
			{
				if( texture.getGlTextureTarget() == this.context.gl.TEXTURE_2D )
					fragStr.push( "uniform sampler2D uSampler" + texture.getResourceHandle() + ";\n" );
				else
					fragStr.push( "uniform samplerCube uSamplerCube" + texture.getResourceHandle() + ";\n" );
			
				fragStr.push( "varying vec3 vTextureCoords" + i + ";\n" );
			}
		}
		
		this._uniformStrings = { vs : vertStr.join( "" ), fs : fragStr.join( "" ) };
	}
	,
	_generateShaderImplement : function()
	{
		var vertStr = [];
		var fragStr = [];
		var layers = this.getLayers(), texture, layer;
		
		//vs
		for( var i = 0; i < layers.length; i ++ )
		{
			layer = this._layers[i];
			
			if( layer.mapinput == SmitearWebGL.Resources.MaterialLayer.MAP_NORM )
			{
				vertStr.push( "texturePos = normal;\n" );
			}
			else if( layer.mapinput == SmitearWebGL.Resources.MaterialLayer.MAP_REF )
			{
				vertStr.push( "texturePos = reflect(eyevec, normal);\n" );
			}
			else if( layer.mapinput == SmitearWebGL.Resources.MaterialLayer.MAP_ENV )
			{
				vertStr.push( "texturePos = reflect(-eyevec, normal);\n" );
			}
//			else if( layer.mapinput == SmitearWebGL.Resources.MaterialLayer.UV1 || layer.mapinput == SmitearWebGL.Resources.MaterialLayer.UV2 )
//			{
//				vertStr.push( "texturePos = " + SmitearWebGL.Resources.Buffer.AttribLocation.TEXCOORD + ";\n" );
//			}

			vertStr.push( "vTextureCoords" + i + " = (uLayer" + layer.getResourceHandle() + "Matrix * vec4(texturePos, 1.0)).xyz;\n" );
		}
		
		//fs
		if ( layers.length == 0 )
		{
			fragStr.push( "vec2 texCoord = vec2((gl_FragCoord.x - 0.5) / uWndWidth, (gl_FragCoord.y - 0.5) / uWndHeight);\n" );
			fragStr.push( "vec4 diffMap = texture2D(uDiffMap, texCoord);\n" );
			fragStr.push( "vec4 specMap = texture2D(uSpecMap, texCoord);\n" );
			fragStr.push( "vec4 aoMap = texture2D(uAOMap, texCoord);\n" );
			fragStr.push( "vec4 color = vec4(aoMap.rgb * (uAmbient * uAmbientColor + diffMap.rgb * uDiffuse + specMap.rgb * uSpecular + uEmissive), 1.0);\n" );
			if ( this.context.capacity.supported.OES_texture_float )
			{
				fragStr.push( "gl_FragColor = color;\n" );
			}
			else
			{
				fragStr.push( "float maxValue = max(max(color.r, color.g), color.b);\n" );
				fragStr.push( "if(maxValue > 1.0)\n" );
				fragStr.push( "{\n" );
				fragStr.push( "color.rgb /= maxValue;\n" );
				fragStr.push( "color.a = 1.0 / maxValue;\n" );
				fragStr.push( "}\n" );
				fragStr.push( "gl_FragColor = color;\n" );
			}
		}
		else
		{
			fragStr.push( "vec3 textureCoords = vec3(0.0,0.0,0.0);\n" );
			fragStr.push( "vec3 textureHeight = vec3(0.0,0.0,0.0);\n" );
			fragStr.push( "vec3 l_diffuse = vec3(0.0,0.0,0.0);\n" );
			fragStr.push( "vec3 l_specular = vec3(0.0,0.0,0.0);\n" );
			fragStr.push( "vec3 l_emissive = vec3(0.0,0.0,0.0);\n" );
			fragStr.push( "vec4 l_texture;\n" );
			
			var hasMapToDiffuse = false;
			var hasMapToSpecular = false;
			var hasMapToEmissive = false;
			
			for ( i = 0; i < layers.length; i ++ )
			{
				layer = layers[i];
				texture = layer.getTexture();
				
				if ( texture )
				{
					if ( layer.isMapToHeight() )
					{
						if ( texture.getGlTextureTarget() == this.context.gl.TEXTURE_2D )
							fragStr.push( "textureHeight = texture2D(uSampler" + texture.getResourceHandle() + ", vTextureCoords" + i + ".xy).rgb;\n" );
						else
							fragStr.push( "textureHeight = textureCube(uSamplerCube" + texture.getResourceHandle() + ", vTextureCoords" + i + ").rgb;\n" );
					}
					
					fragStr.push( "textureCoords = vTextureCoords" + i + " + textureHeight;\n" );
					
					if ( texture.getGlTextureTarget() == this.context.gl.TEXTURE_2D )
						fragStr.push( "l_texture = texture2D(uSampler" + texture.getResourceHandle() + ", textureCoords.xy);\n" );
					else
						fragStr.push( "l_texture = textureCube(uSamplerCube" + texture.getResourceHandle() + ", textureCoords);\n" );
				
					if ( layer.isMapToDiffuse() )
					{
						hasMapToDiffuse = true;
						if ( layer.blendMode == SmitearWebGL.Resources.MaterialLayer.BL_MUL )
							fragStr.push( "l_diffuse = mix(l_diffuse, l_diffuse * l_texture.rgb, l_texture.a);\n" );
						else
						{
							if ( layers.length == 1 )
								fragStr.push( "l_diffuse = l_texture.rgb;\n" );
							else
								fragStr.push( "l_diffuse = mix(l_diffuse, l_texture.rgb, l_texture.a);\n" );
						}
					}
					
					if ( layer.isMapToSpecular() )
					{
						hasMapToSpecular = true;
						if ( layer.blendMode == SmitearWebGL.Resources.MaterialLayer.BL_MUL )
							fragStr.push( "l_specular = mix(l_specular, l_specular * l_texture.rgb, l_texture.a);\n" );
						else
						{
							if ( layers.length == 1 )
								fragStr.push( "l_specular = l_texture.rgb;\n" );
							else
								fragStr.push( "l_specular = mix(l_specular, l_texture.rgb, l_texture.a);\n" );
						}
					}
					
					if ( layer.isMapToEmissive() )
					{
						hasMapToEmissive = true;
						if ( layer.blendMode == SmitearWebGL.Resources.MaterialLayer.BL_MUL )
							fragStr.push( "l_emissive = mix(l_emissive, l_emissive * l_texture.rgb, l_texture.a);\n" );
						else
						{
							if ( layers.length == 1 )
								fragStr.push( "l_emissive = l_texture.rgb;\n" );
							else
								fragStr.push( "l_emissive = mix(l_emissive, l_texture.rgb, l_texture.a);\n" );
						}	
					}
				}
			}
			
			if ( ! hasMapToDiffuse ) fragStr.push( "l_diffuse = uDiffuse;\n" );
			if ( ! hasMapToSpecular ) fragStr.push( "l_specular = uSpecular;\n" );
			if ( ! hasMapToEmissive ) fragStr.push( "l_emissive = uEmissive;\n" );
			
			fragStr.push( "vec2 texCoord = vec2((gl_FragCoord.x - 0.5) / uWndWidth, (gl_FragCoord.y - 0.5) / uWndHeight);\n" );
			fragStr.push( "vec4 diffMap = texture2D(uDiffMap, texCoord);\n" );
			fragStr.push( "vec4 specMap = texture2D(uSpecMap, texCoord);\n" );
			fragStr.push( "vec4 aoMap = texture2D(uAOMap, texCoord);\n" );
			fragStr.push( "vec4 color = vec4(aoMap.rgb * ((uAmbient * uAmbientColor + diffMap.rgb) * l_diffuse + specMap.rgb * l_specular + l_emissive), 1.0);\n" );
			if ( this.context.capacity.supported.OES_texture_float )
			{
				fragStr.push( "gl_FragColor = color;\n" );
			}
			else
			{
				fragStr.push( "float maxValue = max(max(color.r, color.g), color.b);\n" );
				fragStr.push( "if(maxValue > 1.0)\n" );
				fragStr.push( "{\n" );
				fragStr.push( "color.rgb /= maxValue;\n" );
				fragStr.push( "color.a = 1.0 / maxValue;\n" );
				fragStr.push( "}\n" );
				fragStr.push( "gl_FragColor = color;\n" );
			}
		}
		
		this._implementStrings = { vs : vertStr.join( "" ), fs : fragStr.join( "" ) };
	}
	,
	_createResource : function()
	{
		this._super();
		
		this.ambient = $C( 0.3, 0.3, 0.3, 1.0 );
		this.diffuse = $C( 0.8, 0.8, 0.8, 1.0 );
		this.specular = $C( 0.6, 0.6, 0.6, 1.0 );
		this.emissive = $C( 0.0, 0.0, 0.0, 1.0 );
		this.shininess = 8.0;
		this.reflect = 0.8;
		this.alpha = 1.0;
		this.doubleSide = true;
		this.renderMode = SmitearWebGL.Resources.SurfaceMaterial.renderMode.TRIANGLE;

		this._uniformStrings = null;
		this._implementStrings = null;
		this._layers = [];
	}
	,
	_destroyResource : function()
	{
		this._super();
		
		for ( var i = 0; i < this._layers.length; ++ i )
		{
			this._layers[i].release();
		}

		this.ambient = null;
		this.diffuse = null;
		this.specular = null;
		this.emissive = null;
		this.shininess = 8.0;
		this.reflect = 0.8;
		this.alpha = 1.0;
		this.doubleSide = true;
		this.renderMode = SmitearWebGL.Resources.SurfaceMaterial.renderMode.TRIANGLE;

		this._uniformStrings = null;
		this._implementStrings = null;
		this._layers = null;
	}
}
);

SmitearWebGL.Resources.SurfaceMaterial.fog = 
{
	NONE :		1,
	LINEAR :	2,
	QUADRATIC :	3
}

SmitearWebGL.Resources.SurfaceMaterial.renderMode =
{
	POINT :		0,
	WIREFRAME :	1,
	TRIANGLE :	2
}